/**
 * Copyright (C) 2006 OQube 
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *  
 * author: Arnaud Bailly - abailly AT oqube DOT com
 * created: 14 juin 2006
 */
package speculoos.jndi.mappers;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections.MapUtils;

import speculoos.core.Mapper;
import speculoos.core.MapperException;
import speculoos.utils.TypeHelper;

/**
 * This class allows construction of a query filter from an input object.
 * <p>
 * This class may be used in an input chain to construct a string filter for a
 * LDAP query using the fields of an input object and a mapping descriptor that
 * relates java-side field names to LDAP attributes names.
 * 
 * 
 * @author nono
 * 
 */
public class FilterFromInput implements Mapper {

	/*
	 * a map from java names to ldap names
	 */
	private Map outputMap;

	/**
	 * construct a filter from given map.
	 * 
	 * @param map
	 *            the (inverted) map from LDAP names to Java names.
	 */
	public FilterFromInput(Map map) {
		this.outputMap = MapUtils.invertMap(map);
	}

	/**
	 * cosntruct filter without map.
	 * Note that this constructore produces a filter that is
	 * very likely to fail in input.
	 *
	 */
	public FilterFromInput() {
	}
	
	/**
	 * This method constructs a filter string from the input object using the
	 * output map to translate names, the equality operator and the strng
	 * representation of the field value. This method uses reflection through
	 * the {@see fr.lifl.utils.TypeHelper} class. The environment map is used to
	 * store the result with key "filter". Thi input is not modified by this
	 * method.
	 * 
	 * @see speculoos.core.Mapper#map(java.lang.Object, java.util.Map)
	 */
	public Object map(Object input, Map param) throws MapperException {
		TypeHelper th = TypeHelper.instance;
		StringBuffer sb = new StringBuffer();
		sb.append("(&");
		BeanInfo info;
		try {
			info = Introspector.getBeanInfo(input.getClass());
		} catch (IntrospectionException e) {
			throw new MapperException(getName()
					+ ": cannot access fields info from Introspector", e);
		}
		PropertyDescriptor[] props = info.getPropertyDescriptors();
		for (int i = 0; i < props.length; i++) {
			PropertyDescriptor p = props[i];
			String n = (String) outputMap.get(p.getName());
			if (n == null)
				continue;
			Object o = th.get(input, p.getName());
			/* special handling for collections */
			if (o != null)
				if (o instanceof Collection) {
					for (Iterator it = ((Collection) o).iterator(); it
							.hasNext();)
						sb.append('(').append(n).append('=').append(it.next()).append(
								')');

				} else
					sb.append('(').append(n).append('=').append(o).append(')');

		}
		sb.append(')');
		param.put("filter", sb.toString());
		return input;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.core.Mapper#getName()
	 */
	public String getName() {
		return "FilterFromInput";
	}

	/**
	 * @return Returns the outputMap.
	 */
	public Map getOutputMap() {
		return outputMap;
	}

	/**
	 * Make the output map used for this filter as the inverse of the given map.
	 * <strong>NOTE</strong>: standard maps have LDAP names as key and Java
	 * names as values.
	 * 
	 * @param outputMap
	 *            The outputMap to set. It must be bijective and not contain
	 *            null keys or values.
	 */
	public void setOutputMap(Map outputMap) {
		this.outputMap = MapUtils.invertMap(outputMap);
	}

	/**
	 * Factory method for creating a specialized version of 
	 * this filter.
	 * This method tries to load a subclass of FilterFromInput that 
	 * is specialized for the given Class instance. By convention, the
	 * class looked for is named in the package of <code>cls</code> and 
	 * with class name <code>FilterFromXXX</code> where <code>XXX</code> 
	 * is the simple name of the given class. 
	 * 
	 * @param cls the Class instance to look transformer for.
	 * @param map the Map from LDAP name to Java names.
	 * @return an initialized instance of this class.
	 */
	public static FilterFromInput create(Class cls, Map map) {
		if(map == null)
			throw new IllegalArgumentException("Cannot create filter map from null map");
		if(cls == null)
			return new FilterFromInput(map);
		/* compute class name */
		String cln = cls.getPackage().getName() + ".FilterFrom"+cls.getName().substring(cls.getPackage().getName().length());
		/* load it */
		try {
			Class attrs = Class.forName(cln);
			return (FilterFromInput) attrs.newInstance();
		}catch(Exception e) {
			return new FilterFromInput(map);
		}
	}

}
